package com.lingxu.base.common.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.generators.ECKeyPairGenerator;
import org.bouncycastle.crypto.params.*;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.Properties;

/**
 * @Author: BaiDH
 * @Date: 2021/1/12 15:53
 * @Describe: SM2用于数据库链接解密的工具类
 */
@Slf4j
//@Component
public class SM2DbConnUtil {

    // 参照 https://www.cnblogs.com/alsodzy/p/9854521.html
    private static final String dbConnPrivateKey;
    private static final String dbConnPublicKey;
    private static final Properties prop = new Properties();
    private static final SM2Engine sm2EncryptEngine;
    private static final SM2Engine sm2DecryptEngine;



    static{
        // 初始化时加载
        readProperties("key.properties");
        dbConnPrivateKey=prop.getProperty("dbConnPrivateKey");
        dbConnPublicKey=prop.getProperty("dbConnPublicKey");

        // 获取一条SM2曲线参数
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        // 构造domain参数
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(),
                sm2ECParameters.getG(), sm2ECParameters.getN());
        //提取公钥点
        ECPoint pukPoint = sm2ECParameters.getCurve().decodePoint(hexString2byte(dbConnPublicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);
        sm2EncryptEngine = new SM2Engine();
        sm2EncryptEngine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

        BigInteger privateKeyD = new BigInteger(dbConnPrivateKey, 16);
        ECPrivateKeyParameters privateKeyParameters = new ECPrivateKeyParameters(privateKeyD, domainParameters);

        sm2DecryptEngine = new SM2Engine();
        sm2DecryptEngine.init(false, privateKeyParameters);
    }

    public static void main(String[] args){
        String msg="测试加解密";
        String msg1=encrypt("测试加解密");
        System.out.println(msg1);
        String msg2=decrypt(msg1);
        System.out.println(msg2);
    }

    public static void createKeyPair() throws NoSuchAlgorithmException {
        ////生成密钥对
        X9ECParameters sm2ECParameters = GMNamedCurves.getByName("sm2p256v1");
        ECDomainParameters domainParameters = new ECDomainParameters(sm2ECParameters.getCurve(), sm2ECParameters.getG(), sm2ECParameters.getN());
        ECKeyPairGenerator keyPairGenerator = new ECKeyPairGenerator();
        keyPairGenerator.init(new ECKeyGenerationParameters(domainParameters, SecureRandom.getInstance("SHA1PRNG")));
        AsymmetricCipherKeyPair asymmetricCipherKeyPair = keyPairGenerator.generateKeyPair();

        //私钥，16进制格式，自己保存
        BigInteger privatekey = ((ECPrivateKeyParameters) asymmetricCipherKeyPair.getPrivate()).getD();
        String privateKeyHex = privatekey.toString(16);
        System.out.println("privateKeyHex: "+privateKeyHex);
        //公钥，16进制格式，发给前端
        ECPoint ecPoint = ((ECPublicKeyParameters) asymmetricCipherKeyPair.getPublic()).getQ();
        String publicKeyHex = Hex.toHexString(ecPoint.getEncoded(false));
        System.out.println("publicKeyHex: "+publicKeyHex);
    }

    /**
     * 读取配置文件
     * @param fileName
     */
    public static void readProperties(String fileName){
        try {
            InputStream in = PropertiesUtil.class.getResourceAsStream("/"+fileName);
            BufferedReader bf = new BufferedReader(new InputStreamReader(in));
            prop.load(bf);
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    /**
     * SM2加密算法
     * @param data          数据
     * @return
     */
    public static String encrypt(String data){

        byte[] arrayOfBytes = null;
        try {
            byte[] in = data.getBytes(StandardCharsets.UTF_8);
            byte[] in2 = Base64.getEncoder().encode(in);
            arrayOfBytes = sm2EncryptEngine.processBlock(in2, 0, in2.length);
        } catch (Exception e) {
            log.error("SM2加密时出现异常:", e);
        }
        return Hex.toHexString(arrayOfBytes);
    }

    /**
     * SM2解密算法
     * @param cipherData    密文数据
     * @return
     */
    public static String decrypt(String cipherData) {
        byte[] cipherDataByte = Hex.decode(cipherData);
        String result = null;
        try {
            byte[] arrayOfBytes = Base64.getDecoder().decode(sm2DecryptEngine.processBlock(cipherDataByte, 0, cipherDataByte.length));
            return new String(arrayOfBytes, StandardCharsets.UTF_8);
        } catch (Exception e) {
            log.error("SM2解密时出现异常:", e);
        }
        return result;
    }

    /**
     * @param hexString 16进制字符串    如:"33d20046" 转换为 0x33 0xD2 0x00 0x46
     * @return
     * @Title: hexString2byte
     * @Description: 16进制字符串转字节数组
     * @since: 0.0.1
     */
    public static byte[] hexString2byte(String hexString) {
        if (null == hexString || hexString.length() % 2 != 0 || hexString.contains("null")) {
            return null;
        }
        byte[] bytes = new byte[hexString.length() / 2];
        for (int i = 0; i < hexString.length(); i += 2) {
            bytes[i / 2] = (byte) (Integer.parseInt(hexString.substring(i, i + 2), 16) & 0xff);
        }
        return bytes;
    }
}